1 Introduction

This RMarkdown document is part of the Generic Skills Component (GSK) of the Course of the Foundation Studies Programme at Srishti Manipal Institute of Art, Design, and Technology, Bangalore India. The material is based on A Layered Grammar of Graphics by Hadley Wickham. The course is meant for First Year students pursuing a Degree in Art and Design.

The intent of this GSK part is to build Skill in coding in R, and also appreciate R as a way to metaphorically visualize information of various kinds, using predominantly geometric figures and structures.

All RMarkdown files combine code, text, web-images, and figures developed using code. Everything is text; code chunks are enclosed in fences (```)

2 Goals

At the end of this Lab session, we should: - know the types and structures of spatial data and be able to work with them - understand the basics of modern spatial packages in R - be able to specify and download spatial data from the web, using R from sources such as naturalearth and Open Streep Map - plot static and interactive maps using ggplot, tmap and leaflet packages - add symbols and markers for places and regions of our own interest in these maps. - see directions for further work (e.g. maps + networks together)

3 Pedagogical Note

The method followed will be based on PRIMM:

  • PREDICT Inspect the code and guess at what the code might do, write predictions
  • RUN the code provided and check what happens
  • INFER what the parameters of the code do and write comments to explain. What bells and whistles can you see?
  • MODIFY the parameters code provided to understand the options available. Write comments to show what you have aimed for and achieved.
  • MAKE : take an idea/concept of your own, and graph it.

3.1 Set Up

The setup code chunk below brings into our coding session R packages that provide specific computational abilities and also datasets which we can use.

To reiterate: Packages and datasets are not the same thing !! Packages are (small) collections of programs. Datasets are just….information.

5 Introduction to Maps in R

We will take small steps in making maps using just two of the several map making packages in R.

The steps we will use are:

  1. Search for an area of interest
  2. Learn how to access spatial/map data using osmdata
  3. Plot and dress up our map using ggplot and tmap
  4. Create interactive maps with leaflet using a variety of map data providers. Note: tmap can also do interactive maps which we will explore also.

Bas. Onwards and Map-wards!!

All jargon words will be capitalized and in bold font.

6 Step1 - Specifying an area of interest

In R, we need to specify a “BOUNDING BOX” first, to declare our area of interest. God made me a BengaluR-kaR…I think..Let’s see if we can declare an area of interest. Then we can order on Swiggy and…never mind.

We can declare a BOUNDING BOX in several ways, using a variety of R packages.

  1. Using a rough longitude latitude info directly, or using a place name to search for the long/lat info. THIS WILL ALWAYS WORK if the NUMBERS ARE OK !!
# BLR Bounding Box using `osmplotr` package
# get_bbox is a command from the `osmplotr` package
# get_bbox needs lat and lon ranges
# Format is c(min-lon, min-lat, max-lon, max-lat)
bbox_1 <- osmplotr::get_bbox(latlon = c(77.46,12.83,77.74,13.14))
bbox_1
##     min   max
## x 77.46 77.74
## y 12.83 13.14
  1. Using a place name to look up a BOUNDING BOX. This may not always work if the place name is know well known.
# Using getbb command from the osmdata package
bbox_2 <- osmdata::getbb("Jayanagar, Bangalore, India")
bbox_2
##        min      max
## x 77.56242 77.60242
## y 12.90927 12.94927
# Using searchbbox command from the prettymapr package
bbox_3 <- prettymapr::searchbbox("Jayanagar, Bangalore, India")
bbox_3
##        min      max
## x 77.56242 77.60242
## y 12.90927 12.94927

Let us examine all the calculated BOUNDING BOXes

bbox_1
##     min   max
## x 77.46 77.74
## y 12.83 13.14
bbox_2
##        min      max
## x 77.56242 77.60242
## y 12.90927 12.94927
bbox_3
##        min      max
## x 77.56242 77.60242
## y 12.90927 12.94927

bbox_1 is quite big: 0.3 degrees range in both lon and lat. bbox_2 and bbox_3 are identical and also reasonably sized: 0.04 degrees in longitude range and in latitude range.

We will use the bbox_3 from the above, to ensure we have smaller data downloads!

7 Step2 - Get Map data

Within our bbox for Jayanagar, Bangalore, we want to download diverse kinds of FEATURE data. Remember that a FEATURE is any object that can be “seen” on a map. This is done using the osmplotr::extract_osm_objects() command. The main parameters for this command are:

  • bbox
  • KEY / VALUE pairs (“TAGS”) to specify the kind of feature you need
  • return_type if you want a specific kind of geometry to be returned. Say you want only POINT locations of restaurants, and not their buildings

7.1 OSM Tags

See OSM Tags for a nice visual description of popular tags that we can use. Useful keys include:

  • building : yes (all), house residential, apartments
  • highway: residential, service, track, unclassified, footway, path
  • amenity: parking, parking_space, bench; place_of_worship; restaurant, cafe, fast_food; school, waste_basket, fuel, bank, toilets…
  • shop: convenience, supermarket, clothes, hairdresser, car-repair…
  • name: actual name of the place e.g. Main_Street, McDonald’s, Pizza Hut, Subway
  • waterway
  • natural
  • boundary

For example see: tag : highway

7.2 Downloading Data from Open Street Map

We will get the map data from OSM and then save it avoid repeated downloads. So, please copy/paste and run the following commands in your console. Do not run these commands too many times. Re-run this ONLY if you have changed your BOUNDING BOX.

dat_buildings <-extract_osm_objects (key = "building", 
                                     bbox = bbox_3,)

dat_roads <- extract_osm_objects (key = 'highway', 
                                     value = c("residential"),
                                     bbox = bbox_3)

dat_parks <- extract_osm_objects (key = 'park', 
                                  bbox = bbox_3)

dat_greenery <- extract_osm_objects (key = 'landuse', 
                                  value = 'grass', 
                                  bbox = bbox_3)

dat_trees <- extract_osm_objects (key = 'natural', 
                                  value = 'tree', 
                                  bbox = bbox_3)

7.2.1 Let us save this data!

We will store the downloaded data as .gpkg files on our local hard drives to use when we run this file again later.

st_write(dat_buildings, 
         dsn = "buildings.gpkg", 
         append = FALSE, 
         quiet = FALSE)
## Deleting layer `buildings' using driver `GPKG'
## Writing layer `buildings' to data source `buildings.gpkg' using driver `GPKG'
## Writing 34752 features with 81 fields and geometry type Polygon.
st_write(dat_parks, dsn = "parks.gpkg", append = FALSE, quiet = FALSE)
## Deleting layer `parks' using driver `GPKG'
## Writing layer `parks' to data source `parks.gpkg' using driver `GPKG'
## Writing 57 features with 23 fields and geometry type Polygon.
st_write(dat_greenery, dsn = "greenery.gpkg", append = FALSE,quiet = FALSE)
## Deleting layer `greenery' using driver `GPKG'
## Writing layer `greenery' to data source `greenery.gpkg' using driver `GPKG'
## Writing 2 features with 2 fields and geometry type Polygon.
st_write(dat_trees, dsn = "trees.gpkg", append = FALSE,quiet = FALSE)
## Deleting layer `trees' using driver `GPKG'
## Writing layer `trees' to data source `trees.gpkg' using driver `GPKG'
## Writing 153 features with 8 fields and geometry type Point.
st_write(dat_roads, dsn = "roads.gpkg", append = FALSE, quiet = FALSE)
## Deleting layer `roads' using driver `GPKG'
## Writing layer `roads' to data source `roads.gpkg' using driver `GPKG'
## Writing 2161 features with 27 fields and geometry type Line String.

Always work from here to avoid repeated downloads from OSM.

7.2.2 Reading Back the saved Data

buildings <- st_read("./buildings.gpkg")
## Reading layer `buildings' from data source 
##   `C:\Users\Arvind\My Drive\R work\MyWebsites\R-for-Artists-and-Designers\static\labs\buildings.gpkg' 
##   using driver `GPKG'
## Simple feature collection with 34752 features and 81 fields
## Geometry type: POLYGON
## Dimension:     XY
## Bounding box:  xmin: 77.56221 ymin: 12.90906 xmax: 77.60373 ymax: 12.9497
## Geodetic CRS:  WGS 84
parks <- st_read("./parks.gpkg")
## Reading layer `parks' from data source 
##   `C:\Users\Arvind\My Drive\R work\MyWebsites\R-for-Artists-and-Designers\static\labs\parks.gpkg' 
##   using driver `GPKG'
## Simple feature collection with 57 features and 23 fields
## Geometry type: POLYGON
## Dimension:     XY
## Bounding box:  xmin: 77.56196 ymin: 12.90692 xmax: 77.60389 ymax: 12.95397
## Geodetic CRS:  WGS 84
greenery <- st_read("./greenery.gpkg")
## Reading layer `greenery' from data source 
##   `C:\Users\Arvind\My Drive\R work\MyWebsites\R-for-Artists-and-Designers\static\labs\greenery.gpkg' 
##   using driver `GPKG'
## Simple feature collection with 2 features and 2 fields
## Geometry type: POLYGON
## Dimension:     XY
## Bounding box:  xmin: 77.56776 ymin: 12.91751 xmax: 77.57392 ymax: 12.94811
## Geodetic CRS:  WGS 84
trees <- st_read("./trees.gpkg")
## Reading layer `trees' from data source 
##   `C:\Users\Arvind\My Drive\R work\MyWebsites\R-for-Artists-and-Designers\static\labs\trees.gpkg' 
##   using driver `GPKG'
## Simple feature collection with 153 features and 8 fields
## Geometry type: POINT
## Dimension:     XY
## Bounding box:  xmin: 77.56566 ymin: 12.90806 xmax: 77.60096 ymax: 12.94914
## Geodetic CRS:  WGS 84
roads <- st_read("./roads.gpkg")
## Reading layer `roads' from data source 
##   `C:\Users\Arvind\My Drive\R work\MyWebsites\R-for-Artists-and-Designers\static\labs\roads.gpkg' 
##   using driver `GPKG'
## Simple feature collection with 2161 features and 27 fields
## Geometry type: LINESTRING
## Dimension:     XY
## Bounding box:  xmin: 77.55895 ymin: 12.90634 xmax: 77.60603 ymax: 12.95636
## Geodetic CRS:  WGS 84

7.2.3 Let’s look at the data

How many rows? ( Rows -> Features ) What kind of geom column?

# How many buildings?
nrow(buildings)
## [1] 34752
buildings$geom
## Geometry set for 34752 features 
## Geometry type: POLYGON
## Dimension:     XY
## Bounding box:  xmin: 77.56221 ymin: 12.90906 xmax: 77.60373 ymax: 12.9497
## Geodetic CRS:  WGS 84
## First 5 geometries:
class(buildings$geom)
## [1] "sfc_POLYGON" "sfc"

So the buildings dataset has 34752 buildings and their geometry is naturally a POLYGON type of geometry column.

7.2.4 Your Turn 1

Do this check for all the other spatial data, in the code chunk below. What kind of geom column does each dataset have?

7.2.5 What Other Kinds of Data could we get from OSM?

osm_structures returns a data.frame of OSM structure types, associated key-value pairs and unique suffixes which may be appended to data structures/filenames for storage purposes, and suggested colours.

osmplotr::osm_structures()

We can use these key-value pairs to download different types of map data.

8 My first Map in R

There are two ways of plotting maps that we will learn:

8.1 ggplot and geom_sf()

First we will plot with ggplot and geom_sf() : recall that our data is stored in 5 files: buildings, parks, roads, trees, and greenery.

ggplot() +
  geom_sf(data = buildings, colour = "orange") +  # POLYGONS
  geom_sf(data = roads, col = "gray20") +     # LINES
  geom_sf(data = parks, col = "darkseagreen1") + # POLYGONS
  geom_sf(data = greenery, col = "darkseagreen") +  # POLYGONS
  geom_sf(data = trees, col = "green")        # POINTS

Note how geom_sf is capable of handling any geometry in the sfc column !!

geom_sf() is an unusual geom because it will draw different geometric objects depending on what simple features are present in the data: you can get points, lines, or polygons.

So there, we have our first map!

8.2 Map using tmap package

We can also create a map using a package called tmap. Here we also have the option of making the map interactive.

tmap plots are made with code in “groups”: each group starts with a tm_shape() command.

# Group-1
tm_shape(buildings) +
  tm_fill(col = "mediumblue") +

#Group-2
tm_shape(roads) +
  tm_lines(col = "gold") +

#Group-3  
tm_shape(greenery) +
  tm_polygons(col = "limegreen") +
  
#Group-4  
tm_shape(parks) +
  tm_polygons(col = "limegreen") +

#Group-5  
tm_shape(trees) +
  tm_dots(col = "green")

How do we make this map interactive? One more line of code !! Add this line in your console and then run the above chunk again

tmap_mode(“view”)

9 Using data from tmap

Like many other packages ( e.g. ggplot ) tmap also has a few built-in spatial datasets: World and metro, rivers, land and a few others. Check help on these. Let’s plot a first map using datasets built into tmap.

data("World")
head(World, n = 3)

We have several 14 attribute variables in World. Attribute variables such as gdp_cap_est, HPI are numeric. Others such as income_grp appear to be factors. iso_a3 is the standard three letter name for the country. name is of course, the name for each country!

data("metro")
head(metro, n = 3)

Here too we have attribute variables for the metros, and they seem predominantly numeric. Again iso_a3 is the three letter name for the city.

tmap_mode("plot") # Making this a static plot

# Group 1
tm_shape(World) + # dataset = World. 
    tm_polygons("HPI") + # Colour polygons by HPI numeric variable

  # Note the "+" sign continuation
  
# Group 2
tm_shape(metro) + # dataset = metro
  tm_bubbles(size = "pop2030", 
             col = "red") 

# Plot cities as bubbles
# Size proportional to numeric variable `pop2030`
tmap_mode("view") # Change to Interactive


# Let's use WaterColor Map this time!!
tm_tiles("Stamen.Watercolor") + # Watercolor map only with interactive
tm_shape(World) +
    tm_polygons("HPI") + # Color by Happiness Index
  
  
tm_shape(metro) + 
  tm_bubbles(size = "pop2030", # Size City Markers by Population in 2020
             col = "red") 

9.1 Using data from rnaturalearth

The rnaturalearth package allows us to download shapes of countries. We can use it to get borders and also internal state/district boundaries.

india <- 
  ne_states(country =  "india", 
            returnclass = "sf") # gives a ready sf dataframe !

india_neighbours <- 
  ne_states(country = (c("sri lanka", "pakistan",
                         "afghanistan", "nepal","bangladesh", "bhutan")
                       ),
            returnclass = "sf")

Let’s look at the attribute variable columns to colour our graph and to shape our symbols:

names(india)
##  [1] "featurecla" "scalerank"  "adm1_code"  "diss_me"    "iso_3166_2"
##  [6] "wikipedia"  "iso_a2"     "adm0_sr"    "name"       "name_alt"  
## [11] "name_local" "type"       "type_en"    "code_local" "code_hasc" 
## [16] "note"       "hasc_maybe" "region"     "region_cod" "provnum_ne"
## [21] "gadm_level" "check_me"   "datarank"   "abbrev"     "postal"    
## [26] "area_sqkm"  "sameascity" "labelrank"  "name_len"   "mapcolor9" 
## [31] "mapcolor13" "fips"       "fips_alt"   "woe_id"     "woe_label" 
## [36] "woe_name"   "latitude"   "longitude"  "sov_a3"     "adm0_a3"   
## [41] "adm0_label" "admin"      "geonunit"   "gu_a3"      "gn_id"     
## [46] "gn_name"    "gns_id"     "gns_name"   "gn_level"   "gn_region" 
## [51] "gn_a1_code" "region_sub" "sub_code"   "gns_level"  "gns_lang"  
## [56] "gns_adm1"   "gns_region" "min_label"  "max_label"  "min_zoom"  
## [61] "wikidataid" "name_ar"    "name_bn"    "name_de"    "name_en"   
## [66] "name_es"    "name_fr"    "name_el"    "name_hi"    "name_hu"   
## [71] "name_id"    "name_it"    "name_ja"    "name_ko"    "name_nl"   
## [76] "name_pl"    "name_pt"    "name_ru"    "name_sv"    "name_tr"   
## [81] "name_vi"    "name_zh"    "ne_id"      "geometry"
names(india_neighbours)
##  [1] "featurecla" "scalerank"  "adm1_code"  "diss_me"    "iso_3166_2"
##  [6] "wikipedia"  "iso_a2"     "adm0_sr"    "name"       "name_alt"  
## [11] "name_local" "type"       "type_en"    "code_local" "code_hasc" 
## [16] "note"       "hasc_maybe" "region"     "region_cod" "provnum_ne"
## [21] "gadm_level" "check_me"   "datarank"   "abbrev"     "postal"    
## [26] "area_sqkm"  "sameascity" "labelrank"  "name_len"   "mapcolor9" 
## [31] "mapcolor13" "fips"       "fips_alt"   "woe_id"     "woe_label" 
## [36] "woe_name"   "latitude"   "longitude"  "sov_a3"     "adm0_a3"   
## [41] "adm0_label" "admin"      "geonunit"   "gu_a3"      "gn_id"     
## [46] "gn_name"    "gns_id"     "gns_name"   "gn_level"   "gn_region" 
## [51] "gn_a1_code" "region_sub" "sub_code"   "gns_level"  "gns_lang"  
## [56] "gns_adm1"   "gns_region" "min_label"  "max_label"  "min_zoom"  
## [61] "wikidataid" "name_ar"    "name_bn"    "name_de"    "name_en"   
## [66] "name_es"    "name_fr"    "name_el"    "name_hi"    "name_hu"   
## [71] "name_id"    "name_it"    "name_ja"    "name_ko"    "name_nl"   
## [76] "name_pl"    "name_pt"    "name_ru"    "name_sv"    "name_tr"   
## [81] "name_vi"    "name_zh"    "ne_id"      "geometry"
# Look only at attributes
india %>% st_drop_geometry() %>% head()
india_neighbours%>% st_drop_geometry() %>% head()

In the india data frame: - Column iso_a2 contains the country name. - Column name contains the name of the state

In the india_neighbours data frame: - Column gu_a3 contains the country abbreviation - Column name contains the name of the state - Column iso_3166_2 contains the abbreviation of the state within each neighbouring country.

9.1.1 Map 1

tmap_mode("view")

# Plot India
  tm_shape(india) +
  tm_polygons("name", # Colour by States in India
              legend.show = FALSE) +
  
# Plot Neighbours
  tm_shape(india_neighbours) +
  tm_fill(col = "gu_a3") +  # Colour by Country Name
  
# Plot the cities in India alone
  tm_shape(metro %>% dplyr::filter(iso_a3 == "IND")) +
    
  tm_dots(size = "pop2020",legend.size.show = FALSE) +
    # size by population in 2020
    
  tm_layout(legend.show = FALSE) +
  tm_credits("Geographical Boundaries are not accurate",
             size = 0.5,
             position = "right") +
  tm_compass(position = c("right", "top")) +
  tm_scale_bar(position = "left") +
  tmap_style(style = "classic") 
## Warning: Number of levels of the variable "name" is 35, which is
## larger than max.categories (which is 30), so levels are combined. Set
## tmap_options(max.categories = 35) in the layer function to show all levels.
#Try other map styles
#cobalt #gray #white #watercolor #beaver #classic #watercolor #albatross #bw #col_blind

9.2 Your Turn 2

Can you try to download a map area of your home town and plot it as we have above?

9.3 Adding my favourite Restaurants to the map

Is it time to order on Swiggy…

Let us adding interesting places to our map: say based on your favourite restaurants etc. We need restaurant data: lat/long + name + maybe type of restaurant. This can be manually created ( like all of OSMdata ) or if it is already there we can download using key-value pairs in our OSM data query. Restaurants can be downloaded using key= "amenity", value = "restaurant". Since we want JUST their location, and not the restaurant BUILDINGs, we say return_type = "points".

There are also other tags to explore!Searching for McDonalds for instance…( key = “name”, value = “McDonalds”)

dat_R <- extract_osm_objects(bbox = bbox_3, 
                             key = "amenity", 
                             value = "restaurant", 
                             return_type = "point") #<<
# Save the data for future use
write_sf(dat_R, dsn = "restaurants.gpkg",append = FALSE, quiet = FALSE)
## Deleting layer `restaurants' using driver `GPKG'
## Writing layer `restaurants' to data source `restaurants.gpkg' using driver `GPKG'
## Writing 205 features with 33 fields and geometry type Point.

Note the return_type parameter: we want the location and not the building in which the restaurant is!!

9.4 Reading the saved Restaurant Data

restaurants <- st_read("./restaurants.gpkg")
## Reading layer `restaurants' from data source 
##   `C:\Users\Arvind\My Drive\R work\MyWebsites\R-for-Artists-and-Designers\static\labs\restaurants.gpkg' 
##   using driver `GPKG'
## Simple feature collection with 205 features and 33 fields
## Geometry type: POINT
## Dimension:     XY
## Bounding box:  xmin: 77.56373 ymin: 12.9105 xmax: 77.60104 ymax: 12.94917
## Geodetic CRS:  WGS 84

How many restaurants have we got?

restaurants %>% nrow()
## [1] 205

These are the columns in the Restaurant Data:

names(restaurants)
##  [1] "osm_id"             "name"               "addr.city"         
##  [4] "addr.housename"     "addr.housenumber"   "addr.postcode"     
##  [7] "addr.street"        "alt_name"           "amenity"           
## [10] "building"           "capacity"           "cuisine"           
## [13] "delivery"           "description"        "diet.vegetarian"   
## [16] "email"              "food"               "internet_access"   
## [19] "level"              "name.en"            "name.kn"           
## [22] "note"               "opening_hours"      "operator"          
## [25] "phone"              "smoking"            "source"            
## [28] "takeaway"           "toilets.wheelchair" "website"           
## [31] "wheelchair"         "wikidata"           "wikipedia"         
## [34] "geom"

So let us plot the restaurants as POINTs using the restaurants data we have downloaded. The cuisine attribute looks interesting; let us colour the POINT based on the cuisine offered at that restaurant.

So Let’s look therefore at the cuisine column!

# ( I want pizza...)
restaurants$cuisine
##   [1] NA                                       
##   [2] NA                                       
##   [3] "indian"                                 
##   [4] "italian"                                
##   [5] NA                                       
##   [6] "indian"                                 
##   [7] "indian"                                 
##   [8] "regional"                               
##   [9] "indian"                                 
##  [10] NA                                       
##  [11] NA                                       
##  [12] NA                                       
##  [13] NA                                       
##  [14] NA                                       
##  [15] NA                                       
##  [16] NA                                       
##  [17] "pizza"                                  
##  [18] NA                                       
##  [19] NA                                       
##  [20] NA                                       
##  [21] NA                                       
##  [22] NA                                       
##  [23] "regional"                               
##  [24] "ice_cream"                              
##  [25] "ice_cream"                              
##  [26] NA                                       
##  [27] NA                                       
##  [28] NA                                       
##  [29] "indian"                                 
##  [30] "chinese"                                
##  [31] NA                                       
##  [32] NA                                       
##  [33] "pizza"                                  
##  [34] NA                                       
##  [35] "burger;sandwich"                        
##  [36] NA                                       
##  [37] "chinese"                                
##  [38] NA                                       
##  [39] NA                                       
##  [40] NA                                       
##  [41] NA                                       
##  [42] NA                                       
##  [43] NA                                       
##  [44] NA                                       
##  [45] NA                                       
##  [46] NA                                       
##  [47] NA                                       
##  [48] "indian"                                 
##  [49] NA                                       
##  [50] "italian"                                
##  [51] "regional"                               
##  [52] NA                                       
##  [53] "indian"                                 
##  [54] "indian"                                 
##  [55] "italian"                                
##  [56] "regional"                               
##  [57] "indian"                                 
##  [58] "chinese"                                
##  [59] "indian"                                 
##  [60] NA                                       
##  [61] "indian"                                 
##  [62] "indian"                                 
##  [63] "indian"                                 
##  [64] NA                                       
##  [65] "indian"                                 
##  [66] NA                                       
##  [67] NA                                       
##  [68] "ice_cream"                              
##  [69] "pizza"                                  
##  [70] NA                                       
##  [71] "South_Indian"                           
##  [72] "regional"                               
##  [73] "regional"                               
##  [74] NA                                       
##  [75] NA                                       
##  [76] NA                                       
##  [77] NA                                       
##  [78] NA                                       
##  [79] NA                                       
##  [80] NA                                       
##  [81] NA                                       
##  [82] NA                                       
##  [83] NA                                       
##  [84] NA                                       
##  [85] NA                                       
##  [86] NA                                       
##  [87] NA                                       
##  [88] NA                                       
##  [89] NA                                       
##  [90] NA                                       
##  [91] NA                                       
##  [92] NA                                       
##  [93] NA                                       
##  [94] NA                                       
##  [95] NA                                       
##  [96] "Multi-cuisne"                           
##  [97] "South_India"                            
##  [98] "indian"                                 
##  [99] "indian"                                 
## [100] "chicken;regional"                       
## [101] NA                                       
## [102] "arab"                                   
## [103] "indian"                                 
## [104] NA                                       
## [105] NA                                       
## [106] NA                                       
## [107] "regional"                               
## [108] "regional"                               
## [109] NA                                       
## [110] "regional"                               
## [111] "regional"                               
## [112] "regional"                               
## [113] "indian;seafood;fine_dining"             
## [114] "indian"                                 
## [115] "indian"                                 
## [116] "indian"                                 
## [117] "regional"                               
## [118] "regional"                               
## [119] "italian"                                
## [120] "regional"                               
## [121] "regional"                               
## [122] "regional"                               
## [123] "regional"                               
## [124] "regional"                               
## [125] "regional"                               
## [126] "regional"                               
## [127] "regional"                               
## [128] "regional"                               
## [129] "regional"                               
## [130] "regional"                               
## [131] "regional"                               
## [132] "fast_food"                              
## [133] "indian"                                 
## [134] "regional"                               
## [135] "italian"                                
## [136] "regional"                               
## [137] "regional"                               
## [138] "regional"                               
## [139] "regional"                               
## [140] "regional"                               
## [141] "regional"                               
## [142] "italian"                                
## [143] "fast_food"                              
## [144] "regional"                               
## [145] "fast_food"                              
## [146] "regional"                               
## [147] "chinese"                                
## [148] NA                                       
## [149] "regional"                               
## [150] "regional"                               
## [151] "regional"                               
## [152] "regional"                               
## [153] "regional"                               
## [154] "regional"                               
## [155] "regional"                               
## [156] "regional"                               
## [157] "regional"                               
## [158] "regional"                               
## [159] "regional"                               
## [160] "regional"                               
## [161] "regional"                               
## [162] "regional"                               
## [163] "regional"                               
## [164] "regional"                               
## [165] "regional"                               
## [166] "regional"                               
## [167] "regional"                               
## [168] "regional"                               
## [169] "kebab;grill"                            
## [170] "chicken"                                
## [171] "chinese;sandwich;tea;indian;coffee_shop"
## [172] NA                                       
## [173] "indian,_japanese"                       
## [174] "italian"                                
## [175] NA                                       
## [176] NA                                       
## [177] NA                                       
## [178] NA                                       
## [179] NA                                       
## [180] NA                                       
## [181] NA                                       
## [182] NA                                       
## [183] NA                                       
## [184] NA                                       
## [185] NA                                       
## [186] NA                                       
## [187] NA                                       
## [188] NA                                       
## [189] NA                                       
## [190] NA                                       
## [191] NA                                       
## [192] NA                                       
## [193] NA                                       
## [194] NA                                       
## [195] NA                                       
## [196] NA                                       
## [197] NA                                       
## [198] "indian"                                 
## [199] NA                                       
## [200] NA                                       
## [201] NA                                       
## [202] NA                                       
## [203] NA                                       
## [204] NA                                       
## [205] "indian;regional"

Big mess…many NAs, some double entries, separated by commas and semicolons….

The cuisine attribute:
Note: The cuisine variable has more than one entry for a given restaurant. We use tidyr::separate() to make multiple columns out of the cuisine column and retain the first one only. Since the entries are badly entered using both “;” and “,” we need to do this twice ;-() Bad Data entry!!

Let’s get one cuisine entry per restaurant, and drop off the ones that do not mention a cuisine at all:

restaurants <- restaurants %>% 
  drop_na(cuisine) %>% # Knock off nondescript restaurants
  
  # Some have more than one classification ;-()
  # Separated by semicolon or comma, so....
  separate(col = cuisine, into = c("cuisine", NA, NA), sep = ";") %>% 
  separate(col = cuisine, into = c("cuisine", NA, NA), sep = ",")
## Warning: Expected 3 pieces. Additional pieces discarded in 1 rows [104].
## Warning: Expected 3 pieces. Missing pieces filled with `NA` in 106 rows [1, 2,
## 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...].
## Warning: Expected 3 pieces. Missing pieces filled with `NA` in 108 rows [1, 2,
## 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...].
# Finally good food?
restaurants$cuisine
##   [1] "indian"       "italian"      "indian"       "indian"       "regional"    
##   [6] "indian"       "pizza"        "regional"     "ice_cream"    "ice_cream"   
##  [11] "indian"       "chinese"      "pizza"        "burger"       "chinese"     
##  [16] "indian"       "italian"      "regional"     "indian"       "indian"      
##  [21] "italian"      "regional"     "indian"       "chinese"      "indian"      
##  [26] "indian"       "indian"       "indian"       "indian"       "ice_cream"   
##  [31] "pizza"        "South_Indian" "regional"     "regional"     "Multi-cuisne"
##  [36] "South_India"  "indian"       "indian"       "chicken"      "arab"        
##  [41] "indian"       "regional"     "regional"     "regional"     "regional"    
##  [46] "regional"     "indian"       "indian"       "indian"       "indian"      
##  [51] "regional"     "regional"     "italian"      "regional"     "regional"    
##  [56] "regional"     "regional"     "regional"     "regional"     "regional"    
##  [61] "regional"     "regional"     "regional"     "regional"     "regional"    
##  [66] "fast_food"    "indian"       "regional"     "italian"      "regional"    
##  [71] "regional"     "regional"     "regional"     "regional"     "regional"    
##  [76] "italian"      "fast_food"    "regional"     "fast_food"    "regional"    
##  [81] "chinese"      "regional"     "regional"     "regional"     "regional"    
##  [86] "regional"     "regional"     "regional"     "regional"     "regional"    
##  [91] "regional"     "regional"     "regional"     "regional"     "regional"    
##  [96] "regional"     "regional"     "regional"     "regional"     "regional"    
## [101] "regional"     "kebab"        "chicken"      "chinese"      "indian"      
## [106] "italian"      "indian"       "indian"

Looks clean! Each entry is only ONE and not multiple any more. Now let’s plot the Restaurants as POINTs:

# http://www.stat.columbia.edu/~tzheng/files/Rcolor.pdf
# 
ggplot() + 
  geom_sf(data = buildings, colour = "burlywood1") + 
  geom_sf(data = roads, colour = "gray80") +
  geom_sf(data = restaurants %>% drop_na(cuisine), aes(fill = cuisine), colour = "black", shape = 21, size = 3) + 
  theme(legend.position = "right") +
  labs(title = "Restaurants in South Central Bangalore",
       caption = "Based on osmdata")

We could have done a (much!) better job, by combining cuisines into simpler and fewer categories, ( South_India and South_Indian ), but that is for another day!!

By now we know that we can use geom_sf() multiple number of times with different datasets to create layered maps in R.

10 Scope and Packages for Exploration!!

10.0.1 sfnetworks

10.0.2 mapsf

10.0.3 ggspatial

11 Resources

  1. Emine Fidan, Guide to Creating Interactive Maps in R

  2. Nikita Voevodin,R, Not the Best Practices

12 Assignments

  1. Draw a map of your home-town with your favourite restaurants shown. Pop-ups for each restaurant will win bonus points.

12.0.1 Inspiration

  1. Burkhart, Christian. n.d. “Streetmaps.” StreetMaps

  1. Making Vector Maps, Computing for the Social Sciences, Univ. of Chicago
LS0tDQp0aXRsZTogIkxhYi0wNjogVGhlIEdyYW1tYXIgb2YgTWFwcyINCnN1YnRpdGxlOiAiV2hlcmUgaXMgdGhlIFNlY3JldCBHYXJkZW4/Ig0KYXV0aG9yOiAiQXJ2aW5kIFZlbmthdGFkcmkiDQpkYXRlOiAyMi9BcHJpbC8yMDIxDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdGhlbWU6IGZsYXRseQ0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIHRvY19kZXB0aDogMg0KICAgIG51bWJlcl9zZWN0aW9uczogVFJVRQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUNCmFic3RyYWN0OiBQYXJ0IG9mIHRoZSBgUiBmb3IgQXJ0aXN0cyBhbmQgRGVzaWduZXJzYCB3b3Jrc2hvcCBjb3Vyc2UgYXQgdGhlIFNjaG9vbCBvZiBGb3VuZGF0aW9uIFN0dWRpZXMsIFNyaXNodGkgTWFuaXBhbCBJbnN0aXR1dGUgb2YgQXJ0LCBEZXNpZ24sIGFuZCBUZWNobm9sb2d5LCBCYW5nYWxvcmUuDQotLS0NCg0KDQojIEludHJvZHVjdGlvbg0KDQpUaGlzIFJNYXJrZG93biBkb2N1bWVudCBpcyBwYXJ0IG9mIHRoZSBHZW5lcmljIFNraWxscyBDb21wb25lbnQgIChHU0spIG9mIHRoZSBDb3Vyc2Ugb2YgdGhlICBGb3VuZGF0aW9uIFN0dWRpZXMgUHJvZ3JhbW1lIGF0IFNyaXNodGkgTWFuaXBhbCBJbnN0aXR1dGUgb2YgQXJ0LCBEZXNpZ24sIGFuZCBUZWNobm9sb2d5LCBCYW5nYWxvcmUgSW5kaWEuIFRoZSBtYXRlcmlhbCBpcyBiYXNlZCBvbiAqQSBMYXllcmVkIEdyYW1tYXIgb2YgR3JhcGhpY3MqIGJ5IEhhZGxleSBXaWNraGFtLiBUaGUgY291cnNlIGlzIG1lYW50IGZvciBGaXJzdCBZZWFyIHN0dWRlbnRzIHB1cnN1aW5nIGEgRGVncmVlIGluIEFydCBhbmQgRGVzaWduLiANCg0KVGhlIGludGVudCBvZiB0aGlzIEdTSyBwYXJ0IGlzIHRvIGJ1aWxkIFNraWxsIGluIGNvZGluZyBpbiBSLCBhbmQgYWxzbyBhcHByZWNpYXRlIFIgYXMgYSB3YXkgdG8gbWV0YXBob3JpY2FsbHkgdmlzdWFsaXplIGluZm9ybWF0aW9uIG9mIHZhcmlvdXMga2luZHMsIHVzaW5nIHByZWRvbWluYW50bHkgZ2VvbWV0cmljIGZpZ3VyZXMgYW5kIHN0cnVjdHVyZXMuDQoNCkFsbCBSTWFya2Rvd24gZmlsZXMgY29tYmluZSBjb2RlLCB0ZXh0LCB3ZWItaW1hZ2VzLCBhbmQgZmlndXJlcyBkZXZlbG9wZWQgdXNpbmcgY29kZS4gRXZlcnl0aGluZyBpcyB0ZXh0OyBjb2RlIGNodW5rcyBhcmUgZW5jbG9zZWQgaW4gKipmZW5jZXMqKiAoYGBgKQ0KDQoNCiMgR29hbHMNCg0KQXQgdGhlIGVuZCBvZiB0aGlzIExhYiBzZXNzaW9uLCB3ZSBzaG91bGQ6DQotIGtub3cgdGhlIHR5cGVzIGFuZCBzdHJ1Y3R1cmVzIG9mIGBzcGF0aWFsIGRhdGFgIGFuZCBiZSBhYmxlIHRvIHdvcmsgd2l0aCB0aGVtDQotIHVuZGVyc3RhbmQgdGhlIGJhc2ljcyBvZiBtb2Rlcm4gc3BhdGlhbCBwYWNrYWdlcyBpbiBSDQotIGJlIGFibGUgdG8gc3BlY2lmeSBhbmQgZG93bmxvYWQgc3BhdGlhbCBkYXRhIGZyb20gdGhlIHdlYiwgdXNpbmcgUiBmcm9tIHNvdXJjZXMgc3VjaCBhcyBgbmF0dXJhbGVhcnRoYCBhbmQgYE9wZW4gU3RyZWVwIE1hcGANCi0gcGxvdCAqc3RhdGljKiBhbmQgKmludGVyYWN0aXZlKiBtYXBzIHVzaW5nIGBnZ3Bsb3RgLCBgdG1hcGAgYW5kIGBsZWFmbGV0YCBwYWNrYWdlcw0KLSBhZGQgc3ltYm9scyBhbmQgbWFya2VycyBmb3IgcGxhY2VzIGFuZCByZWdpb25zIG9mIG91ciBvd24gaW50ZXJlc3QgaW4gdGhlc2UgbWFwcy4NCi0gc2VlIGRpcmVjdGlvbnMgZm9yIGZ1cnRoZXIgd29yayAoZS5nLiBtYXBzICsgbmV0d29ya3MgdG9nZXRoZXIpDQoNCg0KDQojIFBlZGFnb2dpY2FsIE5vdGUNCg0KVGhlIG1ldGhvZCBmb2xsb3dlZCB3aWxsIGJlIGJhc2VkIG9uDQpbUFJJTU1dKGh0dHBzOi8vYmxvZ3Mua2NsLmFjLnVrL2NzZXIvMjAxNy8wOS8wMS9wcmltbS1hLXN0cnVjdHVyZWQtYXBwcm9hY2gtdG8tdGVhY2hpbmctcHJvZ3JhbW1pbmcvKToNCg0KLSAgICoqUFJFRElDVCoqIEluc3BlY3QgdGhlIGNvZGUgYW5kIGd1ZXNzIGF0IHdoYXQgdGhlIGNvZGUgbWlnaHQgZG8sDQogICAgKip3cml0ZSBwcmVkaWN0aW9ucyoqDQotICAgKipSVU4qKiB0aGUgY29kZSBwcm92aWRlZCBhbmQgY2hlY2sgd2hhdCBoYXBwZW5zDQotICAgKipJTkZFUioqIHdoYXQgdGhlIGBwYXJhbWV0ZXJzYCBvZiB0aGUgY29kZSBkbyBhbmQgKip3cml0ZSBjb21tZW50cyB0byBleHBsYWluKiouIFdoYXQgYmVsbHMgYW5kIHdoaXN0bGVzIGNhbiB5b3Ugc2VlPw0KLSAgICoqTU9ESUZZKiogdGhlIGBwYXJhbWV0ZXJzYCBjb2RlIHByb3ZpZGVkIHRvIHVuZGVyc3RhbmQgdGhlDQogICAgYG9wdGlvbnNgIGF2YWlsYWJsZS4gKipXcml0ZSBjb21tZW50cyoqIHRvIHNob3cgd2hhdCB5b3UgaGF2ZSBhaW1lZCBmb3IgYW5kIGFjaGlldmVkLg0KLSAgICoqTUFLRSoqIDogdGFrZSBhbiBpZGVhL2NvbmNlcHQgb2YgeW91ciBvd24sIGFuZCBncmFwaCBpdC4NCg0KDQojIyBTZXQgVXANCg0KVGhlIGBzZXR1cGAgY29kZSAqKmNodW5rKiogYmVsb3cgYnJpbmdzIGludG8gb3VyIGNvZGluZyBzZXNzaW9uICoqUiBwYWNrYWdlcyoqIHRoYXQgcHJvdmlkZSBzcGVjaWZpYyBjb21wdXRhdGlvbmFsIGFiaWxpdGllcyBhbmQgYWxzbyAqKmRhdGFzZXRzKiogd2hpY2ggd2UgY2FuIHVzZS4gDQoNClRvIHJlaXRlcmF0ZTogUGFja2FnZXMgYW5kIGRhdGFzZXRzIGFyZSAqKm5vdCoqIHRoZSBzYW1lIHRoaW5nICEhIFBhY2thZ2VzIGFyZSAoc21hbGwpIGNvbGxlY3Rpb25zIG9mIHByb2dyYW1zLiBEYXRhc2V0cyBhcmUganVzdC4uLi5pbmZvcm1hdGlvbi4NCg0KIyBQYWNrYWdlIHJlbGF0ZWQgSW5zdHJ1Y3Rpb25zDQoNCiAtIEluc3RhbGwgYWxsIHBhY2thZ2VzIHRoYXQgYXJlIGZsYWdnZWQgYnkgUlN0dWRpbw0KIC0gUnVuIHRoaXMgaW4geW91ciBjb25zb2xlIGZpcnN0Og0KDQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoInJvcGVuc2NpL3JuYXR1cmFsZWFydGhoaXJlcyIpDQoNCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2UgPSBGQUxTRSkNCiMgR2V0dGluZyBNYXAgRGF0YSBpbnRvIFINCmxpYnJhcnkob3NtZGF0YSkgIyBJbXBvcnQgT3BlbiBTdHJlZXQgRGF0YQ0KbGlicmFyeShybmF0dXJhbGVhcnRoKQ0KbGlicmFyeShybmF0dXJhbGVhcnRoZGF0YSkNCg0KIyBSdW4gdGhpcyBpbiB5b3VyIGNvbnNvbGUgZmlyc3QNCiNkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoInJvcGVuc2NpL3JuYXR1cmFsZWFydGhoaXJlcyIpDQpsaWJyYXJ5KHJuYXR1cmFsZWFydGhoaXJlcykNCg0KIyANCmxpYnJhcnkocHJldHR5bWFwcikgIyB0byBzZWFyY2ggZm9yIG1hcCBkYXRhIGJhc2VkIG9uIGxvY2F0aW9uDQoNCiMgUGxvdHRpbmcgTWFwcw0KbGlicmFyeSh0aWR5dmVyc2UpICMgTWFwcyB1c2luZyBnZ3Bsb3QgKyBnZW9tX3NmDQpsaWJyYXJ5KG9zbXBsb3RyKSAjICJCZXNwb2tlIiBNYXBzIHVzaW5nIE9TTSBkYXRhDQpsaWJyYXJ5KHRtYXApICMgVGhlbWF0aWMgTWFwcywgc3RhdGljIGFuZCBpbnRlcmFjdGl2ZQ0KDQojIEZvciBTcGF0aWFsIERhdGEgRnJhbWUgUHJvY2Vzc2luZw0KbGlicmFyeShzZikNCmBgYA0KDQoNCiMgSW50cm9kdWN0aW9uIHRvIE1hcHMgaW4gUg0KDQpXZSB3aWxsIHRha2Ugc21hbGwgc3RlcHMgaW4gbWFraW5nIG1hcHMgdXNpbmcganVzdCB0d28gb2YgdGhlIHNldmVyYWwgbWFwIG1ha2luZyBwYWNrYWdlcyBpbiBSLg0KDQpUaGUgc3RlcHMgd2Ugd2lsbCB1c2UgYXJlOg0KDQoxLiBTZWFyY2ggZm9yIGFuIGFyZWEgb2YgaW50ZXJlc3QNCjIuIExlYXJuIGhvdyB0byBhY2Nlc3Mgc3BhdGlhbC9tYXAgZGF0YSB1c2luZyBgb3NtZGF0YWANCjMuIFBsb3QgYW5kIGRyZXNzIHVwIG91ciBtYXAgdXNpbmcgYGdncGxvdGAgYW5kIGB0bWFwYA0KNC4gQ3JlYXRlIGludGVyYWN0aXZlIG1hcHMgd2l0aCBgbGVhZmxldGAgdXNpbmcgYSB2YXJpZXR5IG9mIG1hcCBkYXRhIHByb3ZpZGVycy4gTm90ZTogYHRtYXBgIGNhbiBhbHNvIGRvIGludGVyYWN0aXZlIG1hcHMgd2hpY2ggd2Ugd2lsbCBleHBsb3JlIGFsc28uIA0KDQpCYXMuIE9ud2FyZHMgYW5kIE1hcC13YXJkcyEhDQoNCkFsbCBqYXJnb24gd29yZHMgd2lsbCBiZSBjYXBpdGFsaXplZCBhbmQgaW4gKipib2xkKiogZm9udC4gDQoNCiMgU3RlcDEgLSBTcGVjaWZ5aW5nIGFuIGFyZWEgb2YgaW50ZXJlc3QNCg0KSW4gUiwgd2UgbmVlZCB0byBzcGVjaWZ5IGEgIioqQk9VTkRJTkcgQk9YKioiIGZpcnN0LCB0byBkZWNsYXJlIG91ciBhcmVhIG9mIGludGVyZXN0LiBHb2QgbWFkZSBtZSBhIEJlbmdhbHVSLWthUi4uLkkgdGhpbmsuLkxldCdzIHNlZSBpZiB3ZSBjYW4gZGVjbGFyZSBhbiBhcmVhIG9mIGludGVyZXN0LiBUaGVuIHdlIGNhbiBvcmRlciBvbiBTd2lnZ3kgYW5kLi4ubmV2ZXIgbWluZC4gDQoNCldlIGNhbiBkZWNsYXJlIGEgKipCT1VORElORyBCT1gqKiBpbiBzZXZlcmFsIHdheXMsIHVzaW5nIGEgdmFyaWV0eSBvZiBSIHBhY2thZ2VzLg0KDQphKSBVc2luZyBhIHJvdWdoIGxvbmdpdHVkZSBsYXRpdHVkZSBpbmZvIGRpcmVjdGx5LCBvciB1c2luZyBhIHBsYWNlIG5hbWUgdG8gc2VhcmNoIGZvciB0aGUgbG9uZy9sYXQgaW5mby4gVEhJUyBXSUxMIEFMV0FZUyBXT1JLIGlmIHRoZSBOVU1CRVJTIEFSRSBPSyAhIQ0KDQpgYGB7ciBJLWFtLWdvaW5nLWhvbWUgMX0NCiMgQkxSIEJvdW5kaW5nIEJveCB1c2luZyBgb3NtcGxvdHJgIHBhY2thZ2UNCiMgZ2V0X2Jib3ggaXMgYSBjb21tYW5kIGZyb20gdGhlIGBvc21wbG90cmAgcGFja2FnZQ0KIyBnZXRfYmJveCBuZWVkcyBsYXQgYW5kIGxvbiByYW5nZXMNCiMgRm9ybWF0IGlzIGMobWluLWxvbiwgbWluLWxhdCwgbWF4LWxvbiwgbWF4LWxhdCkNCmJib3hfMSA8LSBvc21wbG90cjo6Z2V0X2Jib3gobGF0bG9uID0gYyg3Ny40NiwxMi44Myw3Ny43NCwxMy4xNCkpDQpiYm94XzENCg0KYGBgDQoNCg0KYikgVXNpbmcgYSBwbGFjZSBuYW1lIHRvIGxvb2sgdXAgYSBCT1VORElORyBCT1guIFRoaXMgbWF5IG5vdCBhbHdheXMgd29yayBpZiB0aGUgcGxhY2UgbmFtZSBpcyBrbm93IHdlbGwga25vd24uDQoNCmBgYHtyIEktYW0tZ29pbmctaG9tZSAyfQ0KIyBVc2luZyBnZXRiYiBjb21tYW5kIGZyb20gdGhlIG9zbWRhdGEgcGFja2FnZQ0KYmJveF8yIDwtIG9zbWRhdGE6OmdldGJiKCJKYXlhbmFnYXIsIEJhbmdhbG9yZSwgSW5kaWEiKQ0KYmJveF8yDQoNCmBgYA0KDQpgYGB7ciBJLWFtLWdvaW5nLWhvbWUgM30NCiMgVXNpbmcgc2VhcmNoYmJveCBjb21tYW5kIGZyb20gdGhlIHByZXR0eW1hcHIgcGFja2FnZQ0KYmJveF8zIDwtIHByZXR0eW1hcHI6OnNlYXJjaGJib3goIkpheWFuYWdhciwgQmFuZ2Fsb3JlLCBJbmRpYSIpDQpiYm94XzMNCg0KYGBgDQoNCkxldCB1cyBleGFtaW5lIGFsbCB0aGUgY2FsY3VsYXRlZCBCT1VORElORyBCT1hlcw0KDQpgYGB7cn0NCmJib3hfMQ0KYmJveF8yDQpiYm94XzMNCmBgYA0KYGJib3hfMWAgaXMgcXVpdGUgYmlnOiAwLjMgZGVncmVlcyByYW5nZSBpbiBib3RoIGxvbiBhbmQgbGF0LiANCmBiYm94XzJgIGFuZCBgYmJveF8zYCBhcmUgaWRlbnRpY2FsIGFuZCBhbHNvIHJlYXNvbmFibHkgc2l6ZWQ6IDAuMDQgZGVncmVlcyBpbiBsb25naXR1ZGUgcmFuZ2UgYW5kIGluIGxhdGl0dWRlIHJhbmdlLg0KDQpXZSB3aWxsIHVzZSB0aGUgIGBiYm94XzNgIGZyb20gdGhlIGFib3ZlLCB0byBlbnN1cmUgd2UgaGF2ZSBzbWFsbGVyIGRhdGEgZG93bmxvYWRzISANCg0KIyBTdGVwMiAtIEdldCBNYXAgZGF0YQ0KDQpXaXRoaW4gb3VyIGBiYm94YCBmb3IgSmF5YW5hZ2FyLCBCYW5nYWxvcmUsIHdlIHdhbnQgdG8gZG93bmxvYWQgZGl2ZXJzZSBraW5kcyBvZiAqKkZFQVRVUkUqKiBkYXRhLiBSZW1lbWJlciB0aGF0IGEgKipGRUFUVVJFKiogaXMgYW55IG9iamVjdCB0aGF0IGNhbiBiZSAic2VlbiIgb24gYSBtYXAuIFRoaXMgaXMgZG9uZSB1c2luZyB0aGUgYG9zbXBsb3RyOjpleHRyYWN0X29zbV9vYmplY3RzKClgIGNvbW1hbmQuIFRoZSBtYWluIHBhcmFtZXRlcnMgZm9yIHRoaXMgY29tbWFuZCBhcmU6DQoNCi0gYmJveCAgDQotICoqS0VZIC8gVkFMVUUqKiBwYWlycyAoKioiVEFHUyIqKikgdG8gc3BlY2lmeSB0aGUga2luZCBvZiBmZWF0dXJlIHlvdSBuZWVkICANCi0gKipyZXR1cm5fdHlwZSoqIGlmIHlvdSB3YW50IGEgc3BlY2lmaWMga2luZCBvZiBnZW9tZXRyeSB0byBiZSByZXR1cm5lZC4gU2F5IHlvdSB3YW50IG9ubHkgKipQT0lOVCoqIGxvY2F0aW9ucyBvZiByZXN0YXVyYW50cywgYW5kIG5vdCB0aGVpciBidWlsZGluZ3MNCg0KDQojIyBPU00gVGFncw0KU2VlIFtPU00gVGFnc10oaHR0cHM6Ly90YWdpbmZvLm9wZW5zdHJlZXRtYXAub3JnLykgZm9yIGEgbmljZSB2aXN1YWwgZGVzY3JpcHRpb24gb2YgcG9wdWxhciB0YWdzIHRoYXQgd2UgY2FuIHVzZS4gVXNlZnVsIGtleXMgaW5jbHVkZToNCg0KLSBidWlsZGluZyA6IHllcyAoYWxsKSwgaG91c2UgcmVzaWRlbnRpYWwsIGFwYXJ0bWVudHMNCi0gaGlnaHdheTogcmVzaWRlbnRpYWwsIHNlcnZpY2UsIHRyYWNrLCB1bmNsYXNzaWZpZWQsIGZvb3R3YXksIHBhdGgNCi0gYW1lbml0eTogcGFya2luZywgcGFya2luZ19zcGFjZSwgYmVuY2g7IHBsYWNlX29mX3dvcnNoaXA7ICByZXN0YXVyYW50LCBjYWZlLCBmYXN0X2Zvb2Q7IHNjaG9vbCwgd2FzdGVfYmFza2V0LCBmdWVsLCBiYW5rLCB0b2lsZXRzLi4uDQotIHNob3A6IGNvbnZlbmllbmNlLCBzdXBlcm1hcmtldCwgY2xvdGhlcywgaGFpcmRyZXNzZXIsIGNhci1yZXBhaXIuLi4NCi0gbmFtZTogYWN0dWFsIG5hbWUgb2YgdGhlIHBsYWNlIGUuZy4gTWFpbl9TdHJlZXQsICpNY0RvbmFsZCdzLCBQaXp6YSBIdXQsIFN1YndheSoNCi0gd2F0ZXJ3YXkNCi0gbmF0dXJhbA0KLSBib3VuZGFyeQ0KDQpGb3IgZXhhbXBsZSBzZWU6IFt0YWcgOiBoaWdod2F5XShodHRwczovL3RhZ2luZm8ub3BlbnN0cmVldG1hcC5vcmcva2V5cy9oaWdod2F5I3ZhbHVlcykNCg0KIyMgRG93bmxvYWRpbmcgRGF0YSBmcm9tIE9wZW4gU3RyZWV0IE1hcA0KDQpXZSB3aWxsIGdldCB0aGUgbWFwIGRhdGEgZnJvbSBPU00gYW5kIHRoZW4gc2F2ZSBpdCBhdm9pZCByZXBlYXRlZCBkb3dubG9hZHMuIFNvLCBwbGVhc2UgY29weS9wYXN0ZSBhbmQgcnVuIHRoZSBmb2xsb3dpbmcgY29tbWFuZHMgaW4geW91ciBjb25zb2xlLiAqKkRvIG5vdCBydW4gdGhlc2UgY29tbWFuZHMgdG9vIG1hbnkgdGltZXMqKi4gKipSZS1ydW4gdGhpcyAgT05MWSBpZiB5b3UgaGF2ZSBjaGFuZ2VkIHlvdXIgQk9VTkRJTkcgQk9YLioqDQoNCmBgYHtyfQ0KZGF0X2J1aWxkaW5ncyA8LWV4dHJhY3Rfb3NtX29iamVjdHMgKGtleSA9ICJidWlsZGluZyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJib3ggPSBiYm94XzMsKQ0KDQpkYXRfcm9hZHMgPC0gZXh0cmFjdF9vc21fb2JqZWN0cyAoa2V5ID0gJ2hpZ2h3YXknLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IGMoInJlc2lkZW50aWFsIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmJveCA9IGJib3hfMykNCg0KZGF0X3BhcmtzIDwtIGV4dHJhY3Rfb3NtX29iamVjdHMgKGtleSA9ICdwYXJrJywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmJveCA9IGJib3hfMykNCg0KZGF0X2dyZWVuZXJ5IDwtIGV4dHJhY3Rfb3NtX29iamVjdHMgKGtleSA9ICdsYW5kdXNlJywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSAnZ3Jhc3MnLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYm94ID0gYmJveF8zKQ0KDQpkYXRfdHJlZXMgPC0gZXh0cmFjdF9vc21fb2JqZWN0cyAoa2V5ID0gJ25hdHVyYWwnLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9ICd0cmVlJywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmJveCA9IGJib3hfMykNCmBgYA0KDQojIyMgTGV0IHVzIHNhdmUgdGhpcyBkYXRhISANCg0KV2Ugd2lsbCBzdG9yZSB0aGUgZG93bmxvYWRlZCBkYXRhIGFzIGAuZ3BrZ2AgZmlsZXMgb24gb3VyIGxvY2FsIGhhcmQgZHJpdmVzIHRvIHVzZSB3aGVuIHdlIHJ1biB0aGlzIGZpbGUgYWdhaW4gbGF0ZXIuIA0KDQpgYGB7cn0NCnN0X3dyaXRlKGRhdF9idWlsZGluZ3MsIA0KICAgICAgICAgZHNuID0gImJ1aWxkaW5ncy5ncGtnIiwgDQogICAgICAgICBhcHBlbmQgPSBGQUxTRSwgDQogICAgICAgICBxdWlldCA9IEZBTFNFKQ0KDQpzdF93cml0ZShkYXRfcGFya3MsIGRzbiA9ICJwYXJrcy5ncGtnIiwgYXBwZW5kID0gRkFMU0UsIHF1aWV0ID0gRkFMU0UpDQpzdF93cml0ZShkYXRfZ3JlZW5lcnksIGRzbiA9ICJncmVlbmVyeS5ncGtnIiwgYXBwZW5kID0gRkFMU0UscXVpZXQgPSBGQUxTRSkNCnN0X3dyaXRlKGRhdF90cmVlcywgZHNuID0gInRyZWVzLmdwa2ciLCBhcHBlbmQgPSBGQUxTRSxxdWlldCA9IEZBTFNFKQ0KDQpzdF93cml0ZShkYXRfcm9hZHMsIGRzbiA9ICJyb2Fkcy5ncGtnIiwgYXBwZW5kID0gRkFMU0UsIHF1aWV0ID0gRkFMU0UpDQpgYGANCg0KQWx3YXlzIHdvcmsgZnJvbSBoZXJlIHRvIGF2b2lkIHJlcGVhdGVkIGRvd25sb2FkcyBmcm9tIE9TTS4NCg0KIyMjIFJlYWRpbmcgQmFjayB0aGUgc2F2ZWQgRGF0YQ0KDQpgYGB7cn0NCmJ1aWxkaW5ncyA8LSBzdF9yZWFkKCIuL2J1aWxkaW5ncy5ncGtnIikNCnBhcmtzIDwtIHN0X3JlYWQoIi4vcGFya3MuZ3BrZyIpDQpncmVlbmVyeSA8LSBzdF9yZWFkKCIuL2dyZWVuZXJ5Lmdwa2ciKQ0KdHJlZXMgPC0gc3RfcmVhZCgiLi90cmVlcy5ncGtnIikNCnJvYWRzIDwtIHN0X3JlYWQoIi4vcm9hZHMuZ3BrZyIpDQoNCmBgYA0KDQoNCiMjIyBMZXQncyBsb29rIGF0IHRoZSBkYXRhDQoNCkhvdyBtYW55IHJvd3M/ICggUm93cyAtPiBGZWF0dXJlcyApDQpXaGF0IGtpbmQgb2YgYGdlb21gIGNvbHVtbj8NCg0KYGBge3J9DQojIEhvdyBtYW55IGJ1aWxkaW5ncz8NCm5yb3coYnVpbGRpbmdzKQ0KYnVpbGRpbmdzJGdlb20NCmNsYXNzKGJ1aWxkaW5ncyRnZW9tKQ0KYGBgDQoNClNvIHRoZSBgYnVpbGRpbmdzYCBkYXRhc2V0IGhhcyBgciBucm93KGJ1aWxkaW5ncylgIGJ1aWxkaW5ncyBhbmQgdGhlaXIgZ2VvbWV0cnkgaXMgbmF0dXJhbGx5IGEgUE9MWUdPTiB0eXBlIG9mIGdlb21ldHJ5IGNvbHVtbi4gDQoNCg0KIyMjIFlvdXIgVHVybiAxDQoNCkRvIHRoaXMgY2hlY2sgZm9yIGFsbCB0aGUgb3RoZXIgc3BhdGlhbCBkYXRhLCBpbiB0aGUgY29kZSBjaHVuayBiZWxvdy4gDQpXaGF0IGtpbmQgb2YgYGdlb21gIGNvbHVtbiBkb2VzIGVhY2ggZGF0YXNldCBoYXZlPw0KDQoNCmBgYHtyIFlPVVItVFVSTi0xLCBpbmNsdWRlPVRSVUV9DQoNCg0KDQpgYGANCg0KDQoNCg0KIyMjIFdoYXQgT3RoZXIgS2luZHMgb2YgRGF0YSBjb3VsZCB3ZSBnZXQgZnJvbSBPU00/DQoNCmBvc21fc3RydWN0dXJlc2AgcmV0dXJucyBhIGRhdGEuZnJhbWUgb2YgT1NNIHN0cnVjdHVyZSB0eXBlcywgYXNzb2NpYXRlZCAqKmtleS12YWx1ZSoqIHBhaXJzIGFuZCB1bmlxdWUgc3VmZml4ZXMgd2hpY2ggbWF5IGJlIGFwcGVuZGVkIHRvIGRhdGEgc3RydWN0dXJlcy9maWxlbmFtZXMgZm9yIHN0b3JhZ2UgcHVycG9zZXMsIGFuZCBzdWdnZXN0ZWQgY29sb3Vycy4NCg0KYGBge3J9DQpvc21wbG90cjo6b3NtX3N0cnVjdHVyZXMoKQ0KYGBgDQoNCldlIGNhbiB1c2UgdGhlc2UgKiprZXktdmFsdWUqKiBwYWlycyB0byBkb3dubG9hZCBkaWZmZXJlbnQgdHlwZXMgb2YgbWFwIGRhdGEuDQoNCg0KIyBNeSBmaXJzdCBNYXAgaW4gUg0KDQpUaGVyZSBhcmUgdHdvIHdheXMgb2YgcGxvdHRpbmcgbWFwcyB0aGF0IHdlIHdpbGwgbGVhcm46DQoNCiMjIGdncGxvdCBhbmQgZ2VvbV9zZigpDQoNCkZpcnN0IHdlIHdpbGwgcGxvdCB3aXRoIGBnZ3Bsb3RgIGFuZCBgZ2VvbV9zZigpYCA6IHJlY2FsbCB0aGF0IG91ciBkYXRhIGlzIHN0b3JlZCBpbiA1IGZpbGVzOiBgYnVpbGRpbmdzYCwgYHBhcmtzYCwgYHJvYWRzYCwgYHRyZWVzYCwgYW5kIGBncmVlbmVyeWAuIA0KDQpgYGB7cn0NCg0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBidWlsZGluZ3MsIGNvbG91ciA9ICJvcmFuZ2UiKSArICAjIFBPTFlHT05TDQogIGdlb21fc2YoZGF0YSA9IHJvYWRzLCBjb2wgPSAiZ3JheTIwIikgKyAgICAgIyBMSU5FUw0KICBnZW9tX3NmKGRhdGEgPSBwYXJrcywgY29sID0gImRhcmtzZWFncmVlbjEiKSArICMgUE9MWUdPTlMNCiAgZ2VvbV9zZihkYXRhID0gZ3JlZW5lcnksIGNvbCA9ICJkYXJrc2VhZ3JlZW4iKSArICAjIFBPTFlHT05TDQogIGdlb21fc2YoZGF0YSA9IHRyZWVzLCBjb2wgPSAiZ3JlZW4iKSAgICAgICAgIyBQT0lOVFMNCg0KYGBgDQoNCg0KTm90ZSBob3cgYGdlb21fc2ZgIGlzIGNhcGFibGUgb2YgaGFuZGxpbmcgKmFueSogZ2VvbWV0cnkgaW4gdGhlIGBzZmNgIGNvbHVtbiAhISANCg0KPiBgZ2VvbV9zZigpYCBpcyBhbiB1bnVzdWFsIGdlb20gYmVjYXVzZSBpdCB3aWxsIGRyYXcgZGlmZmVyZW50IGdlb21ldHJpYyBvYmplY3RzIGRlcGVuZGluZyBvbiB3aGF0IHNpbXBsZSBmZWF0dXJlcyBhcmUgcHJlc2VudCBpbiB0aGUgZGF0YTogeW91IGNhbiBnZXQgcG9pbnRzLCBsaW5lcywgb3IgcG9seWdvbnMuDQoNClNvIHRoZXJlLCB3ZSBoYXZlIG91ciBmaXJzdCBtYXAhDQoNCg0KIyMgTWFwIHVzaW5nIGB0bWFwYCBwYWNrYWdlDQoNCldlIGNhbiBhbHNvIGNyZWF0ZSBhIG1hcCB1c2luZyBhIHBhY2thZ2UgY2FsbGVkIGB0bWFwYC4gSGVyZSB3ZSBhbHNvIGhhdmUgdGhlIG9wdGlvbiBvZiBtYWtpbmcgdGhlIG1hcCAqaW50ZXJhY3RpdmUqLg0KDQpgdG1hcGAgcGxvdHMgYXJlIG1hZGUgd2l0aCBjb2RlIGluICJncm91cHMiOiBlYWNoIGdyb3VwIHN0YXJ0cyB3aXRoIGEgYHRtX3NoYXBlKClgIGNvbW1hbmQuIA0KDQpgYGB7ciB1c2luZyB0bWFwfQ0KIyBHcm91cC0xDQp0bV9zaGFwZShidWlsZGluZ3MpICsNCiAgdG1fZmlsbChjb2wgPSAibWVkaXVtYmx1ZSIpICsNCg0KI0dyb3VwLTINCnRtX3NoYXBlKHJvYWRzKSArDQogIHRtX2xpbmVzKGNvbCA9ICJnb2xkIikgKw0KDQojR3JvdXAtMyAgDQp0bV9zaGFwZShncmVlbmVyeSkgKw0KICB0bV9wb2x5Z29ucyhjb2wgPSAibGltZWdyZWVuIikgKw0KICANCiNHcm91cC00ICANCnRtX3NoYXBlKHBhcmtzKSArDQogIHRtX3BvbHlnb25zKGNvbCA9ICJsaW1lZ3JlZW4iKSArDQoNCiNHcm91cC01ICANCnRtX3NoYXBlKHRyZWVzKSArDQogIHRtX2RvdHMoY29sID0gImdyZWVuIikNCmBgYA0KDQoNCkhvdyBkbyB3ZSBtYWtlIHRoaXMgbWFwIGludGVyYWN0aXZlPyBPbmUgbW9yZSBsaW5lIG9mIGNvZGUgISEgQWRkIHRoaXMgbGluZSBpbiB5b3VyIGNvbnNvbGUgYW5kIHRoZW4gcnVuIHRoZSBhYm92ZSBjaHVuayBhZ2Fpbg0KDQp0bWFwX21vZGUoInZpZXciKQ0KDQoNCiMgVXNpbmcgZGF0YSBmcm9tIGB0bWFwYA0KDQpMaWtlIG1hbnkgb3RoZXIgcGFja2FnZXMgKCBlLmcuIGdncGxvdCApIGB0bWFwYCBhbHNvIGhhcyBhIGZldyBidWlsdC1pbiBzcGF0aWFsIGRhdGFzZXRzOiBgV29ybGRgIGFuZCBgbWV0cm9gLCBgcml2ZXJzYCwgYGxhbmRgIGFuZCBhIGZldyBvdGhlcnMuIENoZWNrIGhlbHAgb24gdGhlc2UuDQpMZXQncyBwbG90IGEgZmlyc3QgbWFwIHVzaW5nIGRhdGFzZXRzIGJ1aWx0IGludG8gYHRtYXBgLiANCg0KYGBge3IgV29ybGQtRGF0YS10bWFwfQ0KZGF0YSgiV29ybGQiKQ0KaGVhZChXb3JsZCwgbiA9IDMpDQpgYGANCg0KV2UgaGF2ZSBzZXZlcmFsIDE0IGF0dHJpYnV0ZSB2YXJpYWJsZXMgaW4gYFdvcmxkYC4gQXR0cmlidXRlIHZhcmlhYmxlcyBzdWNoIGFzIGBnZHBfY2FwX2VzdGAsIGBIUElgIGFyZSBudW1lcmljLiBPdGhlcnMgc3VjaCBhcyBgaW5jb21lX2dycGAgYXBwZWFyIHRvIGJlIGZhY3RvcnMuIA0KYGlzb19hM2AgaXMgdGhlIHN0YW5kYXJkIHRocmVlIGxldHRlciBuYW1lIGZvciB0aGUgY291bnRyeS4gDQpgbmFtZWAgaXMgb2YgY291cnNlLCB0aGUgbmFtZSBmb3IgZWFjaCBjb3VudHJ5IQ0KDQoNCmBgYHtyIFdvcmxkLW1ldHJvLWNpdGllcy10bWFwfQ0KZGF0YSgibWV0cm8iKQ0KaGVhZChtZXRybywgbiA9IDMpDQpgYGANCg0KSGVyZSB0b28gd2UgaGF2ZSBhdHRyaWJ1dGUgdmFyaWFibGVzIGZvciB0aGUgbWV0cm9zLCBhbmQgdGhleSBzZWVtIHByZWRvbWluYW50bHkgbnVtZXJpYy4gQWdhaW4gYGlzb19hM2AgaXMgdGhlIHRocmVlIGxldHRlciBuYW1lIGZvciB0aGUgY2l0eS4NCg0KDQoNCmBgYHtyIE15LVN0YXRpYy1Xb3JsZH0NCnRtYXBfbW9kZSgicGxvdCIpICMgTWFraW5nIHRoaXMgYSBzdGF0aWMgcGxvdA0KDQojIEdyb3VwIDENCnRtX3NoYXBlKFdvcmxkKSArICMgZGF0YXNldCA9IFdvcmxkLiANCiAgICB0bV9wb2x5Z29ucygiSFBJIikgKyAjIENvbG91ciBwb2x5Z29ucyBieSBIUEkgbnVtZXJpYyB2YXJpYWJsZQ0KDQogICMgTm90ZSB0aGUgIisiIHNpZ24gY29udGludWF0aW9uDQogIA0KIyBHcm91cCAyDQp0bV9zaGFwZShtZXRybykgKyAjIGRhdGFzZXQgPSBtZXRybw0KICB0bV9idWJibGVzKHNpemUgPSAicG9wMjAzMCIsIA0KICAgICAgICAgICAgIGNvbCA9ICJyZWQiKSANCiMgUGxvdCBjaXRpZXMgYXMgYnViYmxlcw0KIyBTaXplIHByb3BvcnRpb25hbCB0byBudW1lcmljIHZhcmlhYmxlIGBwb3AyMDMwYA0KYGBgDQoNCg0KDQpgYGB7ciBNeSBJbnRlcmFjdGl2ZSBXYXRlciBDb2xvdXIgV29ybGR9DQp0bWFwX21vZGUoInZpZXciKSAjIENoYW5nZSB0byBJbnRlcmFjdGl2ZQ0KDQoNCiMgTGV0J3MgdXNlIFdhdGVyQ29sb3IgTWFwIHRoaXMgdGltZSEhDQp0bV90aWxlcygiU3RhbWVuLldhdGVyY29sb3IiKSArICMgV2F0ZXJjb2xvciBtYXAgb25seSB3aXRoIGludGVyYWN0aXZlDQp0bV9zaGFwZShXb3JsZCkgKw0KICAgIHRtX3BvbHlnb25zKCJIUEkiKSArICMgQ29sb3IgYnkgSGFwcGluZXNzIEluZGV4DQogIA0KICANCnRtX3NoYXBlKG1ldHJvKSArIA0KICB0bV9idWJibGVzKHNpemUgPSAicG9wMjAzMCIsICMgU2l6ZSBDaXR5IE1hcmtlcnMgYnkgUG9wdWxhdGlvbiBpbiAyMDIwDQogICAgICAgICAgICAgY29sID0gInJlZCIpIA0KYGBgDQoNCiMjIFVzaW5nIGRhdGEgZnJvbSBgcm5hdHVyYWxlYXJ0aGANCg0KVGhlIGBybmF0dXJhbGVhcnRoYCBwYWNrYWdlIGFsbG93cyB1cyB0byBkb3dubG9hZCBzaGFwZXMgb2YgY291bnRyaWVzLiBXZSBjYW4gdXNlIGl0IHRvIGdldCBib3JkZXJzIGFuZCBhbHNvIGludGVybmFsIHN0YXRlL2Rpc3RyaWN0IGJvdW5kYXJpZXMuIA0KDQpgYGB7ciBzcGF0aWFsX2RhdGF9DQppbmRpYSA8LSANCiAgbmVfc3RhdGVzKGNvdW50cnkgPSAgImluZGlhIiwgDQogICAgICAgICAgICByZXR1cm5jbGFzcyA9ICJzZiIpICMgZ2l2ZXMgYSByZWFkeSBzZiBkYXRhZnJhbWUgIQ0KDQppbmRpYV9uZWlnaGJvdXJzIDwtIA0KICBuZV9zdGF0ZXMoY291bnRyeSA9IChjKCJzcmkgbGFua2EiLCAicGFraXN0YW4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJhZmdoYW5pc3RhbiIsICJuZXBhbCIsImJhbmdsYWRlc2giLCAiYmh1dGFuIikNCiAgICAgICAgICAgICAgICAgICAgICAgKSwNCiAgICAgICAgICAgIHJldHVybmNsYXNzID0gInNmIikNCg0KYGBgDQoNCg0KTGV0J3MgbG9vayBhdCB0aGUgYXR0cmlidXRlIHZhcmlhYmxlIGNvbHVtbnMgdG8gY29sb3VyIG91ciBncmFwaCBhbmQgdG8gc2hhcGUgb3VyIHN5bWJvbHM6DQoNCmBgYHtyfQ0KbmFtZXMoaW5kaWEpDQpuYW1lcyhpbmRpYV9uZWlnaGJvdXJzKQ0KDQojIExvb2sgb25seSBhdCBhdHRyaWJ1dGVzDQppbmRpYSAlPiUgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSBoZWFkKCkNCmluZGlhX25laWdoYm91cnMlPiUgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSBoZWFkKCkNCmBgYA0KDQpJbiB0aGUgYGluZGlhYCBkYXRhIGZyYW1lOiANCi0gQ29sdW1uIGBpc29fYTJgIGNvbnRhaW5zIHRoZSBjb3VudHJ5IG5hbWUuIA0KLSBDb2x1bW4gYG5hbWVgIGNvbnRhaW5zIHRoZSBuYW1lIG9mIHRoZSBzdGF0ZQ0KDQpJbiB0aGUgYGluZGlhX25laWdoYm91cnNgIGRhdGEgZnJhbWU6IA0KLSBDb2x1bW4gYGd1X2EzYCBjb250YWlucyB0aGUgY291bnRyeSBhYmJyZXZpYXRpb24NCi0gQ29sdW1uIGBuYW1lYCBjb250YWlucyB0aGUgbmFtZSBvZiB0aGUgc3RhdGUNCi0gQ29sdW1uIGBpc29fMzE2Nl8yYCBjb250YWlucyB0aGUgYWJicmV2aWF0aW9uIG9mIHRoZSBzdGF0ZSB3aXRoaW4gZWFjaCBuZWlnaGJvdXJpbmcgY291bnRyeS4NCg0KDQojIyMgTWFwIDENCmBgYHtyIE1hcF8xfQ0KdG1hcF9tb2RlKCJ2aWV3IikNCg0KIyBQbG90IEluZGlhDQogIHRtX3NoYXBlKGluZGlhKSArDQogIHRtX3BvbHlnb25zKCJuYW1lIiwgIyBDb2xvdXIgYnkgU3RhdGVzIGluIEluZGlhDQogICAgICAgICAgICAgIGxlZ2VuZC5zaG93ID0gRkFMU0UpICsNCiAgDQojIFBsb3QgTmVpZ2hib3Vycw0KICB0bV9zaGFwZShpbmRpYV9uZWlnaGJvdXJzKSArDQogIHRtX2ZpbGwoY29sID0gImd1X2EzIikgKyAgIyBDb2xvdXIgYnkgQ291bnRyeSBOYW1lDQogIA0KIyBQbG90IHRoZSBjaXRpZXMgaW4gSW5kaWEgYWxvbmUNCiAgdG1fc2hhcGUobWV0cm8gJT4lIGRwbHlyOjpmaWx0ZXIoaXNvX2EzID09ICJJTkQiKSkgKw0KICAgIA0KICB0bV9kb3RzKHNpemUgPSAicG9wMjAyMCIsbGVnZW5kLnNpemUuc2hvdyA9IEZBTFNFKSArDQogICAgIyBzaXplIGJ5IHBvcHVsYXRpb24gaW4gMjAyMA0KICAgIA0KICB0bV9sYXlvdXQobGVnZW5kLnNob3cgPSBGQUxTRSkgKw0KICB0bV9jcmVkaXRzKCJHZW9ncmFwaGljYWwgQm91bmRhcmllcyBhcmUgbm90IGFjY3VyYXRlIiwNCiAgICAgICAgICAgICBzaXplID0gMC41LA0KICAgICAgICAgICAgIHBvc2l0aW9uID0gInJpZ2h0IikgKw0KICB0bV9jb21wYXNzKHBvc2l0aW9uID0gYygicmlnaHQiLCAidG9wIikpICsNCiAgdG1fc2NhbGVfYmFyKHBvc2l0aW9uID0gImxlZnQiKSArDQogIHRtYXBfc3R5bGUoc3R5bGUgPSAiY2xhc3NpYyIpIA0KDQojVHJ5IG90aGVyIG1hcCBzdHlsZXMNCiNjb2JhbHQgI2dyYXkgI3doaXRlICN3YXRlcmNvbG9yICNiZWF2ZXIgI2NsYXNzaWMgI3dhdGVyY29sb3IgI2FsYmF0cm9zcyAjYncgI2NvbF9ibGluZA0KDQpgYGANCg0KDQojIyBZb3VyIFR1cm4gMg0KDQpDYW4geW91IHRyeSB0byBkb3dubG9hZCBhIG1hcCBhcmVhIG9mIHlvdXIgaG9tZSB0b3duIGFuZCBwbG90IGl0IGFzIHdlIGhhdmUgYWJvdmU/DQoNCmBgYHtyIFlvdXItVHVybi0yLCBpbmNsdWRlPVRSVUV9DQoNCmBgYA0KDQoNCiMjIEFkZGluZyBteSBmYXZvdXJpdGUgUmVzdGF1cmFudHMgdG8gdGhlIG1hcA0KSXMgaXQgdGltZSB0byBvcmRlciBvbiBTd2lnZ3kuLi4NCg0KTGV0IHVzIGFkZGluZyBpbnRlcmVzdGluZyBwbGFjZXMgdG8gb3VyIG1hcDogc2F5IGJhc2VkIG9uIHlvdXIgZmF2b3VyaXRlIHJlc3RhdXJhbnRzIGV0Yy4gV2UgbmVlZCByZXN0YXVyYW50IGRhdGE6IGxhdC9sb25nICsgbmFtZSArIG1heWJlIHR5cGUgb2YgcmVzdGF1cmFudC4gVGhpcyBjYW4gYmUgbWFudWFsbHkgY3JlYXRlZCAoIGxpa2UgYWxsIG9mIE9TTWRhdGEgKSBvciBpZiBpdCBpcyBhbHJlYWR5IHRoZXJlIHdlIGNhbiBkb3dubG9hZCB1c2luZyAqa2V5LXZhbHVlKiBwYWlycyBpbiBvdXIgT1NNIGRhdGEgcXVlcnkuDQpSZXN0YXVyYW50cyBjYW4gYmUgZG93bmxvYWRlZCB1c2luZyBga2V5PSAiYW1lbml0eSIsIHZhbHVlID0gInJlc3RhdXJhbnQiYC4gU2luY2Ugd2Ugd2FudCBKVVNUIHRoZWlyIGxvY2F0aW9uLCBhbmQgbm90IHRoZSByZXN0YXVyYW50IEJVSUxESU5Hcywgd2Ugc2F5IGByZXR1cm5fdHlwZSA9ICJwb2ludHMiYC4NCg0KVGhlcmUgYXJlIGFsc28gb3RoZXIgdGFncyB0byBleHBsb3JlIVNlYXJjaGluZyBmb3IgTWNEb25hbGRzIGZvciBpbnN0YW5jZS4uLigga2V5ID0gIm5hbWUiLCB2YWx1ZSA9ICJNY0RvbmFsZHMiKQ0KDQpgYGB7ciByZXN0YXVyYW50LWRhdGEtMX0NCmRhdF9SIDwtIGV4dHJhY3Rfb3NtX29iamVjdHMoYmJveCA9IGJib3hfMywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtleSA9ICJhbWVuaXR5IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gInJlc3RhdXJhbnQiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuX3R5cGUgPSAicG9pbnQiKSAjPDwNCiMgU2F2ZSB0aGUgZGF0YSBmb3IgZnV0dXJlIHVzZQ0Kd3JpdGVfc2YoZGF0X1IsIGRzbiA9ICJyZXN0YXVyYW50cy5ncGtnIixhcHBlbmQgPSBGQUxTRSwgcXVpZXQgPSBGQUxTRSkNCg0KYGBgDQoNCk5vdGUgdGhlIGByZXR1cm5fdHlwZWAgcGFyYW1ldGVyOiB3ZSB3YW50IHRoZSBsb2NhdGlvbiBhbmQgKipub3QqKiB0aGUgKipidWlsZGluZyoqIGluIHdoaWNoIHRoZSByZXN0YXVyYW50IGlzISENCg0KIyMgUmVhZGluZyB0aGUgc2F2ZWQgUmVzdGF1cmFudCBEYXRhDQoNCmBgYHtyfQ0KcmVzdGF1cmFudHMgPC0gc3RfcmVhZCgiLi9yZXN0YXVyYW50cy5ncGtnIikNCmBgYA0KDQoNCkhvdyBtYW55IHJlc3RhdXJhbnRzIGhhdmUgd2UgZ290Pw0KYGBge3IgcmVzdGF1cmFudC1kYXRhLTJ9DQoNCnJlc3RhdXJhbnRzICU+JSBucm93KCkNCmBgYA0KDQpUaGVzZSBhcmUgdGhlIGNvbHVtbnMgaW4gdGhlIFJlc3RhdXJhbnQgRGF0YToNCmBgYHtyIHJlc3RhdXJhbnQtZGF0YS0zfQ0KDQpuYW1lcyhyZXN0YXVyYW50cykNCg0KYGBgDQoNClNvIGxldCB1cyBwbG90IHRoZSByZXN0YXVyYW50cyBhcyBQT0lOVHMgdXNpbmcgdGhlIGByZXN0YXVyYW50c2AgZGF0YSB3ZSBoYXZlIGRvd25sb2FkZWQuIFRoZSBgY3Vpc2luZWAgYXR0cmlidXRlIGxvb2tzIGludGVyZXN0aW5nOyBsZXQgdXMgY29sb3VyIHRoZSBQT0lOVCBiYXNlZCBvbiB0aGUgYGN1aXNpbmVgIG9mZmVyZWQgYXQgdGhhdCByZXN0YXVyYW50LiANCg0KU28gTGV0J3MgbG9vayB0aGVyZWZvcmUgYXQgdGhlIGBjdWlzaW5lYCBjb2x1bW4hIA0KDQpgYGB7ciByZXN0YXVyYW50LWRhdGEtNH0NCiMgKCBJIHdhbnQgcGl6emEuLi4pDQpyZXN0YXVyYW50cyRjdWlzaW5lDQpgYGANCkJpZyBtZXNzLi4ubWFueSBOQXMsIHNvbWUgZG91YmxlIGVudHJpZXMsIHNlcGFyYXRlZCBieSBjb21tYXMgYW5kIHNlbWljb2xvbnMuLi4uDQoNCjxkaXYgY2xhc3M9InBhbmVsIHBhbmVsLXN1Y2Nlc3MiPg0KICA8ZGl2IGNsYXNzPSJwYW5lbC1oZWFkaW5nIj5UaGUgYGN1aXNpbmVgIGF0dHJpYnV0ZTo8L2Rpdj4NCiAgPGRpdiBjbGFzcz0icGFuZWwtYm9keSI+DQpOb3RlOiBUaGUgYGN1aXNpbmVgIHZhcmlhYmxlIGhhcyBtb3JlIHRoYW4gb25lIGVudHJ5IGZvciBhIGdpdmVuIHJlc3RhdXJhbnQuICBXZSB1c2UgYHRpZHlyOjpzZXBhcmF0ZSgpYCB0byBtYWtlIG11bHRpcGxlIGNvbHVtbnMgb3V0IG9mIHRoZSBjdWlzaW5lIGNvbHVtbiBhbmQgcmV0YWluIHRoZSBmaXJzdCBvbmUgb25seS4gDQpTaW5jZSB0aGUgZW50cmllcyBhcmUgYmFkbHkgZW50ZXJlZCB1c2luZyBib3RoICI7IiBhbmQgIiwiIHdlIG5lZWQgdG8gZG8gdGhpcyB0d2ljZSA7LSgpDQpCYWQgRGF0YSBlbnRyeSEhDQogIDwvZGl2Pg0KPC9kaXY+DQoNCkxldCdzIGdldCBvbmUgY3Vpc2luZSBlbnRyeSBwZXIgcmVzdGF1cmFudCwgYW5kIGRyb3Agb2ZmIHRoZSBvbmVzIHRoYXQgZG8gbm90IG1lbnRpb24gYSBjdWlzaW5lIGF0IGFsbDoNCg0KYGBge3J9DQpyZXN0YXVyYW50cyA8LSByZXN0YXVyYW50cyAlPiUgDQogIGRyb3BfbmEoY3Vpc2luZSkgJT4lICMgS25vY2sgb2ZmIG5vbmRlc2NyaXB0IHJlc3RhdXJhbnRzDQogIA0KICAjIFNvbWUgaGF2ZSBtb3JlIHRoYW4gb25lIGNsYXNzaWZpY2F0aW9uIDstKCkNCiAgIyBTZXBhcmF0ZWQgYnkgc2VtaWNvbG9uIG9yIGNvbW1hLCBzby4uLi4NCiAgc2VwYXJhdGUoY29sID0gY3Vpc2luZSwgaW50byA9IGMoImN1aXNpbmUiLCBOQSwgTkEpLCBzZXAgPSAiOyIpICU+JSANCiAgc2VwYXJhdGUoY29sID0gY3Vpc2luZSwgaW50byA9IGMoImN1aXNpbmUiLCBOQSwgTkEpLCBzZXAgPSAiLCIpDQoNCiMgRmluYWxseSBnb29kIGZvb2Q/DQpyZXN0YXVyYW50cyRjdWlzaW5lDQpgYGANCg0KTG9va3MgY2xlYW4hIEVhY2ggZW50cnkgaXMgb25seSBPTkUgYW5kIG5vdCBtdWx0aXBsZSBhbnkgbW9yZS4gTm93IGxldCdzIHBsb3QgdGhlIFJlc3RhdXJhbnRzIGFzIFBPSU5UczoNCg0KYGBge3J9DQojIGh0dHA6Ly93d3cuc3RhdC5jb2x1bWJpYS5lZHUvfnR6aGVuZy9maWxlcy9SY29sb3IucGRmDQojIA0KZ2dwbG90KCkgKyANCiAgZ2VvbV9zZihkYXRhID0gYnVpbGRpbmdzLCBjb2xvdXIgPSAiYnVybHl3b29kMSIpICsgDQogIGdlb21fc2YoZGF0YSA9IHJvYWRzLCBjb2xvdXIgPSAiZ3JheTgwIikgKw0KICBnZW9tX3NmKGRhdGEgPSByZXN0YXVyYW50cyAlPiUgZHJvcF9uYShjdWlzaW5lKSwgYWVzKGZpbGwgPSBjdWlzaW5lKSwgY29sb3VyID0gImJsYWNrIiwgc2hhcGUgPSAyMSwgc2l6ZSA9IDMpICsgDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpICsNCiAgbGFicyh0aXRsZSA9ICJSZXN0YXVyYW50cyBpbiBTb3V0aCBDZW50cmFsIEJhbmdhbG9yZSIsDQogICAgICAgY2FwdGlvbiA9ICJCYXNlZCBvbiBvc21kYXRhIikNCmBgYA0KDQoNCldlIGNvdWxkIGhhdmUgZG9uZSBhIChtdWNoISkgYmV0dGVyIGpvYiwgYnkgY29tYmluaW5nIGN1aXNpbmVzIGludG8gc2ltcGxlciBhbmQgZmV3ZXIgY2F0ZWdvcmllcywgKCBTb3V0aF9JbmRpYSBhbmQgU291dGhfSW5kaWFuICksIGJ1dCB0aGF0IGlzIGZvciBhbm90aGVyIGRheSEhIA0KDQpCeSBub3cgd2Uga25vdyB0aGF0IHdlIGNhbiB1c2UgYGdlb21fc2YoKWAgbXVsdGlwbGUgbnVtYmVyIG9mIHRpbWVzIHdpdGggZGlmZmVyZW50IGRhdGFzZXRzIHRvIGNyZWF0ZSBsYXllcmVkIG1hcHMgaW4gUi4NCg0KDQoNCiMgU2NvcGUgYW5kIFBhY2thZ2VzIGZvciBFeHBsb3JhdGlvbiEhDQoNCiMjIyBzZm5ldHdvcmtzDQojIyMgbWFwc2YNCiMjIyBnZ3NwYXRpYWwNCg0KDQojIFJlc291cmNlcw0KDQoxLiBFbWluZSBGaWRhbiwgW0d1aWRlIHRvIENyZWF0aW5nIEludGVyYWN0aXZlIE1hcHMgaW4gUl0oaHR0cHM6Ly9ib29rZG93bi5vcmcvZW5lbWluZWYvRFJSX0Jvb2tkb3duLykNCg0KMi4gTmlraXRhIFZvZXZvZGluLFtSLCBOb3QgdGhlIEJlc3QgUHJhY3RpY2VzXShodHRwczovL2Jvb2tkb3duLm9yZy92b2V2b2Rpbl9udi9SX05vdF90aGVfQmVzdF9QcmFjdGljZXMvbWFwcy5odG1sKQ0KDQoNCiMgQXNzaWdubWVudHMNCg0KMS4gRHJhdyBhIG1hcCBvZiAqKnlvdXIgaG9tZS10b3duKiogd2l0aCB5b3VyIGZhdm91cml0ZSByZXN0YXVyYW50cyBzaG93bi4gUG9wLXVwcyBmb3IgZWFjaCByZXN0YXVyYW50IHdpbGwgd2luIGJvbnVzIHBvaW50cy4gDQoNCiMjIyBJbnNwaXJhdGlvbg0KDQoxLiBCdXJraGFydCwgQ2hyaXN0aWFuLiBuLmQuIOKAnFN0cmVldG1hcHMu4oCdIFtTdHJlZXRNYXBzXShodHRwczovL2dncGxvdDJ0dXRvci5jb20vdHV0b3JpYWxzL3N0cmVldG1hcHMpDQoNCiFbXShodHRwczovL2dncGxvdDJ0dXRvci5jb20vc3RhdGljLzA5MzQ2NmEwZjk0ZjA0ZjM2ZTJjMDI4ZmU3YWUzZjIzL2ZkODRlL21hcHMucG5nKQ0KDQoyLiAqTWFraW5nIFZlY3RvciBNYXBzKiwgQ29tcHV0aW5nIGZvciB0aGUgU29jaWFsIFNjaWVuY2VzLCBbVW5pdi4gb2YgQ2hpY2Fnb10oaHR0cHM6Ly9jZnNzLnVjaGljYWdvLmVkdS9ub3Rlcy92ZWN0b3ItbWFwcy8pDQo=